/*
Copyright (C) Ariff Ehsan, Abdul Hadi Fikri

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

/*  Author : Ariff Ehsan
 *  Date   : July 11, 2009
 */


#include <system.h>
#include <stdlib.h> 


enum cxbee_state_enums {
	CXBEE_STOP = 0,
	CXBEE_IDLE,
	CXBEE_CONFIG_ADDR,
	CXBEE_CONFIG_MAC,
	CXBEE_CONFIG_TXPOWER,
	CXBEE_CONFIG_CHANNEL,
	CXBEE_CONFIG_WRITE,
	CXBEE_CONFIG_APPLY,
	CXBEE_STARTED,
	
	CXBEE_TX_CONFIG_POWER = 0x90,
	CXBEE_TX,
	
	CXBEE_RX_DELIMITER = 0xa0,
	CXBEE_RX_LENGTH0,
	CXBEE_RX_LENGTH1,
	CXBEE_RX_PAYLOAD,
	CXBEE_RX_CHECKSUM,
};


/** ---------------------------------------------------------------------------
 *                           CXBEE VARIABLES
 *  ---------------------------------------------------------------------------
 */

struct {
	uint8_t state;   // current cxbee state
	uint8_t txpower; // current transmit power
	uint8_t channel; // current channel
	uint8_t id;      // frame id
	
	error_t error;
	
	message_t msg;
	message_t *pmsg;
	
	message_t *txmsg;
	uint16_t tx_dest;
	uint8_t tx_len;
	
	void (*startdone)(error_t error);
	void (*senddone)(error_t error);
	message_t *(*receive)(message_t *data, void *payload, uint8_t len);
} cxbee;


struct {
	uint8_t state;
	uint8_t len;
	uint8_t max_len;
	uint8_t checksum;
	
	message_t msg;
	message_t *pmsg;
} cxbee_rx;

task_t cxbee_task_startdone;
task_t cxbee_task_senddone;

timer_t cxbee_timer;


/** ---------------------------------------------------------------------------
 *                           PROTOTYPE FUNCTIONS
 *  ---------------------------------------------------------------------------
 */

void CXBEE_config(void);

error_t CXBEE_transmit(uint8_t, uint16_t, uint8_t, message_t*, uint8_t);

void CXBEE_commandMY(void);
void CXBEE_commandMM(void);
error_t CXBEE_commandPL(uint8_t);
error_t CXBEE_commandCH(uint8_t);
void CXBEE_commandWR(void);
void CXBEE_commandAC(void);

error_t CXBEE_command(uint8_t api_id, message_t *msg, uint8_t len);

void CXBEE_rxArbitrator(void);
void CXBEE_rxResponse(void);
void CXBEE_rxTxStatus(void);
void CXBEE_rxPacket(void);

void CXBEE_responseMY(uint8_t status);
void CXBEE_responseMM(uint8_t status);
void CXBEE_responsePL(uint8_t status);
void CXBEE_responseCH(uint8_t status);
void CXBEE_responseWR(uint8_t status);
void CXBEE_responseAC(uint8_t status);

void CXBEE_SERIAL0_sendStreamDone(error_t);
void CXBEE_SERIAL0_receive(uint8_t);

void CXBEE_defaultStartDone(error_t);
void CXBEE_defaultSendDone(error_t);
message_t *CXBEE_defaultReceive(message_t *, void *, uint8_t);

void CXBEE_TASK_startDone(void);
void CXBEE_TASK_sendDone(void);

void CXBEE_TIMER_expired(void);


/** ---------------------------------------------------------------------------
 *                           CXBEE INTERFACE
 *  ---------------------------------------------------------------------------
 */

error_t CXBEE_start(void) {
	error_t err = FAIL;
	
	switch (cxbee.state) {
	case CXBEE_STOP:
		CXBEE_config();
		
		err = SUCCESS;
		break;
		
	case CXBEE_CONFIG_ADDR:
	case CXBEE_CONFIG_MAC:
	case CXBEE_CONFIG_TXPOWER:
	case CXBEE_CONFIG_APPLY:
		
		err = EALREADY;
		
		break;
		
	}
	
	return err;
}


error_t CXBEE_send(uint16_t dest, message_t *data, uint8_t len) {
	
	/* check module status */
	if (cxbee.state != CXBEE_STARTED) {
		return EBUSY;
	}
	
	/* check valid destination address */
	if (dest == CXBEE_ADDRESS) {
		return EINVAL;
	}
	
	/* check valid data length */
	if (len == 0 || len > CXBEE_DATA_LENGTH) {
		return ESIZE;
	}
	
	/* keep msg pointer */
	cxbee.txmsg = data;
	
	cxbee_metadata_t *metadata = (cxbee_metadata_t*)(data->metadata);
	
	/* check transmit power level */
	if (metadata->tx_power != cxbee.txpower) {
		cxbee.state = CXBEE_TX_CONFIG_POWER;
		
		cxbee.tx_len = len;
		cxbee.tx_dest = dest;
		
		error_t result = CXBEE_commandPL(metadata->tx_power);
		
		if (result != SUCCESS) {
			cxbee.state = CXBEE_STARTED;
			return result;
		}
		else
			return SUCCESS;
	}
	
	uint8_t option;
	
	if (metadata->ack)
		option = 0;
	else
		option = 0x1;
	
	error_t result = CXBEE_transmit(++(cxbee.id), dest, option, data, len);
	
	if (result != SUCCESS) {
		cxbee.state = CXBEE_STARTED;
	}
	
	return result;
}


void CXBEE_reg(void(*startdone)(error_t),
              void(*senddone)(error_t),
			  message_t *(*receive)(message_t *data, void *payload, uint8_t len)) {
	
	if (startdone != NULL)
		cxbee.startdone = startdone;
	
	if (senddone != NULL)
		cxbee.senddone = senddone;
	
	if (receive != NULL)
		cxbee.receive = receive;
}


/** ---------------------------------------------------------------------------
 *                           CXBEE PACKET API INTERFACE
 *  ---------------------------------------------------------------------------
 */

inline void *CXBEE_getPayload(message_t *msg) {
	atTransmit_t *tx = (atTransmit_t*)(msg->data);
	return tx->data;
}


inline void CXBEE_setAck(message_t *data, BOOL option) {
	cxbee_metadata_t *metadata = (cxbee_metadata_t *)(data->metadata);
	metadata->ack = option;
}


inline void CXBEE_setTxPower(message_t *data, uint8_t level) {
	cxbee_metadata_t *metadata = (cxbee_metadata_t *)(data->metadata);
	metadata->tx_power = level;
}


inline int8_t CXBEE_getRssi(message_t *msg) {
	atReceive_t *rx = (atReceive_t *)(msg->data);
	
	int8_t rssi = rx->rssi;
	rssi = -rssi;
	
	return rssi;
}

inline uint16_t CXBEE_getSource(message_t *msg) {
	atReceive_t *rx = (atReceive_t *)(msg->data);
	
	uint16_t addr = rx->src[0];
	addr <<= 8;
	addr |= rx->src[1];
	
	return addr;
}


/** ---------------------------------------------------------------------------
 *                           CXBEE PRIVATE FUNCTIONS
 *  ---------------------------------------------------------------------------
 */

void CXBEE_config(void) {
	
	/* set default event functions */
	if (cxbee.senddone == NULL)
		cxbee.senddone = CXBEE_defaultSendDone;
	
	if (cxbee.startdone == NULL)
		cxbee.startdone = CXBEE_defaultStartDone;
	
	if (cxbee.receive == NULL)
		cxbee.receive = CXBEE_defaultReceive;
	
	/* initialize cxbee variables */
	cxbee.id = (uint8_t)random();
	cxbee.pmsg = &(cxbee.msg);
	
	cxbee_rx.state = CXBEE_RX_DELIMITER;
	cxbee_rx.pmsg = &(cxbee_rx.msg);
	
	/* register task */
	TASK_reg(&cxbee_task_startdone, CXBEE_TASK_startDone);
	TASK_reg(&cxbee_task_senddone, CXBEE_TASK_sendDone);
	
	/* register timer */
	TIMER_reg(&cxbee_timer, CXBEE_TIMER_expired);
	
	/* initialize serial0 */
	SERIAL0_init();
	
	/* register event functions */
	SERIAL0_reg(NULL, CXBEE_SERIAL0_sendStreamDone, CXBEE_SERIAL0_receive);
	
	/* configure node address */
	CXBEE_commandMY();
}


error_t CXBEE_transmit(uint8_t frameid, uint16_t dest, uint8_t option, message_t *msg, uint8_t len) {
	
	cxbee.state = CXBEE_TX;
	
	atTransmit_t *tx = (atTransmit_t*)(msg->data);
	
	cxbee.id++;
	if (cxbee.id == 0) cxbee.id = 1;
	
	tx->frameId = cxbee.id;
	
	tx->dest[0] = (uint8_t)(dest>>8);
	tx->dest[1] = (uint8_t)(dest);
	
	tx->option = option;
	
	return CXBEE_command(CXBEE_API_TXREQUEST, msg, len + 4);
}


void CXBEE_commandMY(void) {
	cxbee.state = CXBEE_CONFIG_ADDR;
	
	uint8_t *data = (uint8_t*)(cxbee.pmsg->data);
	
	cxbee.id++;
	if (cxbee.id == 0) cxbee.id = 1;
	
	data[0] = cxbee.id;
	data[1] = 'M';
	data[2] = 'Y';
	
	uint16_t addr = CXBEE_ADDRESS;
	data[3] = (uint8_t)(addr>>8);
	data[4] = (uint8_t)(addr);
	
	error_t result = CXBEE_command(CXBEE_API_ATCOMMAND, cxbee.pmsg, 5);
	
	if (result != SUCCESS) {
		cxbee.error = result;
		TASK_post(&cxbee_task_startdone);
	}
}


void CXBEE_commandMM(void) {
	cxbee.state = CXBEE_CONFIG_MAC;
	
	uint8_t *data = (uint8_t*)(cxbee.pmsg->data);
	
	cxbee.id++;
	if (cxbee.id == 0) cxbee.id = 1;
	
	data[0] = cxbee.id;
	data[1] = 'M';
	data[2] = 'M';
	
	data[3] = CXBEE_DEF_MAC;
	
	error_t result = CXBEE_command(CXBEE_API_ATCOMMAND, cxbee.pmsg, 4);
	
	if (result != SUCCESS) {
		cxbee.error = result;
		TASK_post(&cxbee_task_startdone);
	}
}


error_t CXBEE_commandPL(uint8_t level) {
	if (level > 4)
		return EINVAL;
	
	cxbee.txpower = level;
	
	uint8_t *data = (uint8_t*)(cxbee.pmsg->data);
	
	cxbee.id++;
	if (cxbee.id == 0) cxbee.id = 1;
	
	data[0] = cxbee.id;
	data[1] = 'P';
	data[2] = 'L';
	
	data[3] = level;
	
	return CXBEE_command(CXBEE_API_ATCOMMAND, cxbee.pmsg, 4);
}

error_t CXBEE_commandCH(uint8_t channel) {
	if (channel < 11 || channel > 26)
		return EINVAL;
	
	cxbee.channel = channel;
	
	uint8_t *data = (uint8_t*)(cxbee.pmsg->data);
	
	cxbee.id++;
	if (cxbee.id == 0) cxbee.id = 1;
	
	data[0] = cxbee.id;
	data[1] = 'C';
	data[2] = 'H';
	
	data[3] = channel;
	
	return CXBEE_command(CXBEE_API_ATCOMMAND, cxbee.pmsg, 4);
}


void CXBEE_commandWR(void) {
	cxbee.state = CXBEE_CONFIG_WRITE;
	
	uint8_t *data = (uint8_t*)(cxbee.pmsg->data);
	
	cxbee.id++;
	if (cxbee.id == 0) cxbee.id = 1;
	
	data[0] = cxbee.id;
	data[1] = 'W';
	data[2] = 'R';
	
	error_t result = CXBEE_command(CXBEE_API_ATCOMMAND, cxbee.pmsg, 3);
	
	if (result != SUCCESS) {
		cxbee.error = result;
		TASK_post(&cxbee_task_startdone);
	}
}


void CXBEE_commandAC(void) {
	cxbee.state = CXBEE_CONFIG_APPLY;
	
	uint8_t *data = (uint8_t*)(cxbee.pmsg->data);
	
	cxbee.id++;
	if (cxbee.id == 0) cxbee.id = 1;
	
	data[0] = cxbee.id;
	data[1] = 'A';
	data[2] = 'C';
	
	error_t result = CXBEE_command(CXBEE_API_ATCOMMAND, cxbee.pmsg, 3);
	
	if (result != SUCCESS) {
		cxbee.error = result;
		TASK_post(&cxbee_task_startdone);
	}
}


void CXBEE_TASK_startDone(void) {
	
	if (cxbee.error == SUCCESS)
		cxbee.state = CXBEE_STARTED;
	else 
		cxbee.state = CXBEE_STOP;
	
	cxbee.startdone(cxbee.error);
}


error_t CXBEE_command(uint8_t api_id, message_t *msg, uint8_t len) {
	
	atHeader_t *header = (atHeader_t*)(msg->header);
	
	header->delimiter = 0x7e;
	header->len[0] = 0;
	header->len[1] = (len + 1); // + api field
	header->api = api_id;
	
	uint8_t checksum = api_id;
	uint8_t *data = msg->data;
	uint8_t i;
	
	for (i = 0; i < len; i++)
		checksum += data[i];
	
	checksum = 0xff - checksum;
	data[len] = checksum;
	
	error_t error = SERIAL0_sendStream((uint8_t *)msg, sizeof(atHeader_t) + len + 1);
	
	if (error == SUCCESS)
		TIMER_start(&cxbee_timer, CXBEE_COMMAND_TIMEOUT);
	
	return error;
}

void CXBEE_TIMER_expired(void) {
	cxbee.error = EOFF;
	
	switch (cxbee.state) {
	case CXBEE_TX:
	case CXBEE_TX_CONFIG_POWER:
		TASK_post(&cxbee_task_senddone);
		break;
		
	case CXBEE_CONFIG_ADDR:
	case CXBEE_CONFIG_MAC:
	case CXBEE_CONFIG_TXPOWER:
	case CXBEE_CONFIG_CHANNEL:
	case CXBEE_CONFIG_WRITE:
	case CXBEE_CONFIG_APPLY:
		TASK_post(&cxbee_task_startdone);
	}
}


void CXBEE_TASK_sendDone(void) {
	/* update cxbee state */
	cxbee.state = CXBEE_STARTED;
	
	/* signal send done event */
	cxbee.senddone(cxbee.error);
}


void CXBEE_rxArbitrator(void) {
	atHeader_t *header = (atHeader_t *)(cxbee_rx.pmsg->header);
	
	switch (header->api) {
	case CXBEE_API_ATRESPONSE:
		CXBEE_rxResponse();
		break;
		
	case CXBEE_API_TXSTATUS:
		CXBEE_rxTxStatus();
		break;
		
	case CXBEE_API_RXPACKET:
		CXBEE_rxPacket();
		break;
		
	}
}


void CXBEE_rxResponse(void) {
	uint8_t *data = (uint8_t *)(cxbee_rx.pmsg->data);
	
	/* check frame id */
	if (cxbee.id != data[0]) {
		return;
	}
	
	TIMER_stop(&cxbee_timer);
	
	if (data[1] == 'M' && data[2] == 'Y') {
		CXBEE_responseMY(data[3]);
	}
	else if (data[1] == 'M' && data[2] == 'M') {
		CXBEE_responseMM(data[3]);
	}
	else if (data[1] == 'P' && data[2] == 'L') {
		CXBEE_responsePL(data[3]);
	}
	else if (data[1] == 'W' && data[2] == 'R') {
		CXBEE_responseWR(data[3]);
	}
	else if (data[1] == 'A' && data[2] == 'C') {
		CXBEE_responseAC(data[3]);
	}
	else if (data[1] == 'C' && data[2] == 'H') {
		CXBEE_responseCH(data[3]);
	}
}


void CXBEE_responseMY(uint8_t status) {
	
	if (status == 0) { // OK
		/* configure mac mode */
		CXBEE_commandMM();
	}
	else { // ERROR
		cxbee.error = FAIL;
		TASK_post(&cxbee_task_startdone);
	}
}


void CXBEE_responseMM(uint8_t status) {
	
	if (status == 0) {
		/* configure tx power */
		cxbee.state = CXBEE_CONFIG_TXPOWER;
		cxbee.txpower = CXBEE_DEF_TXPOWER;
		
		error_t result = CXBEE_commandPL(CXBEE_DEF_TXPOWER);
		
		if (result != SUCCESS) {
			cxbee.error = result;
			TASK_post(&cxbee_task_startdone);
		}
	}
	else {
		cxbee.error = FAIL;
		TASK_post(&cxbee_task_startdone);
	}
}


void CXBEE_responsePL(uint8_t status) {
	error_t result;
	uint8_t option;
	
	switch (cxbee.state) {
	case CXBEE_CONFIG_TXPOWER:
		if (status == 0) {
			cxbee.state = CXBEE_CONFIG_CHANNEL;
			cxbee.channel = CXBEE_DEF_CHANNEL;
			
			error_t result = CXBEE_commandCH(CXBEE_DEF_CHANNEL);
			
			if (result != SUCCESS) {
				cxbee.error = result;
				TASK_post(&cxbee_task_startdone);
			}
		}
		else {
			cxbee.error = FAIL;
			TASK_post(&cxbee_task_startdone);
		}
		
		break;
		
	case CXBEE_TX_CONFIG_POWER:
		if (status == 0) {
			if (((cxbee_metadata_t*)(cxbee.txmsg->metadata))->ack)
				option = 0;
			else
				option = 0x1;
			
			result = CXBEE_transmit(++(cxbee.id), cxbee.tx_dest, option, cxbee.txmsg, cxbee.tx_len);
			
			if (result != SUCCESS) {
				cxbee.error = FAIL;
				TASK_post(&cxbee_task_senddone);
			}
		}
		else {
			cxbee.error = FAIL;
			TASK_post(&cxbee_task_senddone);
		}
		
		break;
		
	}
}


void CXBEE_responseCH(uint8_t status) {
	
	switch (cxbee.state) {
	case CXBEE_CONFIG_CHANNEL:
		if (status == 0) {
			/* apply configuration */
			CXBEE_commandAC();
		}
		else {
			cxbee.error = FAIL;
			TASK_post(&cxbee_task_startdone);
		}
		
		break;
	}
}


void CXBEE_responseWR(uint8_t status) {
	
	if (status == 0) {
		/* apply changes */
		CXBEE_commandAC();
	}
	else {
		cxbee.error = FAIL;
		TASK_post(&cxbee_task_startdone);
	}
}


void CXBEE_responseAC(uint8_t status) {

	if (status == 0)
		cxbee.error = SUCCESS;
	else
		cxbee.error = FAIL;
	
	TASK_post(&cxbee_task_startdone);
}


void CXBEE_rxTxStatus(void) {
	
	uint8_t *data = (uint8_t*)(cxbee_rx.pmsg->data);
	
	if (data[0] != cxbee.id) {
		return;
	}
	
	TIMER_stop(&cxbee_timer);
	
	if (data[1] == 0)
		cxbee.error = SUCCESS;
	else if (data[1] == 1)
		cxbee.error = ERETRY;
	else if (data[1] == 2)
		cxbee.error = EBUSY;
	else
		cxbee.error = FAIL;
	
	TASK_post(&cxbee_task_senddone);
}


void CXBEE_rxPacket(void) {
	
	if (cxbee.state == CXBEE_STOP)
		return;
	
	/* signal cxbee receive event */
	cxbee_rx.pmsg = cxbee.receive(cxbee_rx.pmsg,
                                   CXBEE_getPayload(cxbee_rx.pmsg),
								   cxbee_rx.max_len - 5);
}


/** ---------------------------------------------------------------------------
 *                           CXBEE SERIAL0 EVENT FUNCTIONS
 *  ---------------------------------------------------------------------------
 */

void CXBEE_SERIAL0_sendStreamDone(error_t error) {
}


void CXBEE_SERIAL0_receive(uint8_t data) {
	uint8_t *rx_data;
	
	switch (cxbee_rx.state) {
	case CXBEE_RX_DELIMITER:
		if (data == 0x7e)
			cxbee_rx.state = CXBEE_RX_LENGTH0;
		
		break;
		
	case CXBEE_RX_LENGTH0:
		if (data != 0)
			cxbee_rx.state = CXBEE_RX_DELIMITER;
		else
			cxbee_rx.state = CXBEE_RX_LENGTH1;
		
		break;
		
	case CXBEE_RX_LENGTH1:
		if (data > CXBEE_DATA_LENGTH)
			cxbee_rx.state = CXBEE_RX_DELIMITER;
		else {
			cxbee_rx.state = CXBEE_RX_PAYLOAD;
			cxbee_rx.max_len = data;
			cxbee_rx.len = 0;
			cxbee_rx.checksum = 0;
		}
		
		break;
		
	case CXBEE_RX_PAYLOAD:
		rx_data = (uint8_t*)(cxbee_rx.pmsg);
		rx_data[3+cxbee_rx.len] = data;
		
		cxbee_rx.checksum += data;
		cxbee_rx.len++;
		
		if (cxbee_rx.len == cxbee_rx.max_len)
			cxbee_rx.state = CXBEE_RX_CHECKSUM;
		
		break;
		
	case CXBEE_RX_CHECKSUM:
		cxbee_rx.checksum += data;
		
		if (cxbee_rx.checksum == 0xff)
			CXBEE_rxArbitrator();
		
		cxbee_rx.state = CXBEE_RX_DELIMITER;
		
		break;
		
	default:
		cxbee_rx.state = CXBEE_RX_DELIMITER;
		break;
		
	}
}


/** ---------------------------------------------------------------------------
 *                           DEFAULT FUNCTIONS
 *  ---------------------------------------------------------------------------
 */

void CXBEE_defaultStartDone(error_t error) {
}


void CXBEE_defaultSendDone(error_t error) {
}


message_t *CXBEE_defaultReceive(message_t *data, void *payload, uint8_t len) {
	return data;
}

